﻿using System.Text;
using System.Xml;

using ThingsGateway.NewLife.Reflection;

namespace ThingsGateway.NewLife.Xml;

/// <summary>Xml辅助类</summary>
public static class XmlHelper
{
    #region 实体转Xml
    /// <summary>序列化为Xml字符串</summary>
    /// <param name="obj">要序列化为Xml的对象</param>
    /// <param name="encoding">编码</param>
    /// <param name="attachComment">是否附加注释，附加成员的Description和DisplayName注释</param>
    /// <param name="useAttribute">是否使用特性输出</param>
    /// <returns>Xml字符串</returns>
    public static String ToXml(this Object obj, Encoding? encoding = null, Boolean attachComment = false, Boolean useAttribute = false)
    {
        if (obj == null) return String.Empty;

        encoding ??= Encoding.UTF8;

        using var stream = new MemoryStream();
        ToXml(obj, stream, encoding, attachComment, useAttribute);

        return stream.ToArray().ToStr();
    }

    /// <summary>序列化为Xml字符串</summary>
    /// <param name="obj">要序列化为Xml的对象</param>
    /// <param name="encoding">编码</param>
    /// <param name="attachComment">是否附加注释，附加成员的Description和DisplayName注释</param>
    /// <param name="useAttribute">是否使用特性输出</param>
    /// <param name="omitXmlDeclaration">忽略XML声明</param>
    /// <returns>Xml字符串</returns>
    public static String ToXml(this Object obj, Encoding encoding, Boolean attachComment, Boolean useAttribute, Boolean omitXmlDeclaration)
    {
        if (obj == null) return String.Empty;

        using var stream = new MemoryStream();
        var xml = new Serialization.Xml
        {
            Stream = stream,
            Encoding = encoding ?? Encoding.UTF8,
            UseAttribute = useAttribute,
            UseComment = attachComment,
            Setting = new XmlWriterSettings
            {
                OmitXmlDeclaration = omitXmlDeclaration,
                Indent = true
            }
        };
        xml.Write(obj);

        return stream.ToArray().ToStr();
    }

    /// <summary>序列化为Xml数据流</summary>
    /// <param name="obj">要序列化为Xml的对象</param>
    /// <param name="stream">目标数据流</param>
    /// <param name="encoding">编码</param>
    /// <param name="attachComment">是否附加注释，附加成员的Description和DisplayName注释</param>
    /// <param name="useAttribute">是否使用特性输出</param>
    public static void ToXml(this Object obj, Stream stream, Encoding? encoding = null, Boolean attachComment = false, Boolean useAttribute = false)
    {
        if (obj == null) return;

        var xml = new Serialization.Xml
        {
            Stream = stream,
            Encoding = encoding ?? Encoding.UTF8,
            UseAttribute = useAttribute,
            UseComment = attachComment
        };
        xml.Write(obj);
    }

    /// <summary>序列化为Xml文件</summary>
    /// <param name="obj">要序列化为Xml的对象</param>
    /// <param name="file">目标Xml文件</param>
    /// <param name="encoding">编码</param>
    /// <param name="attachComment">是否附加注释，附加成员的Description和DisplayName注释</param>
    /// <returns>Xml字符串</returns>
    public static void ToXmlFile(this Object obj, String file, Encoding? encoding = null, Boolean attachComment = true)
    {
        if (File.Exists(file)) File.Delete(file);
        file.EnsureDirectory(true);

        // 如果是字符串字典，直接写入文件，其它设置无效
        if (obj is IDictionary<String, String> dic)
        {
            var xml = dic.ToXml();
            File.WriteAllText(file, xml, encoding ?? Encoding.UTF8);
            return;
        }

        using var stream = new FileStream(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
        obj.ToXml(stream, encoding, attachComment);
        // 必须通过设置文件流长度来实现截断，否则后面可能会多一截旧数据
        stream.SetLength(stream.Position);
        stream.Flush();
    }
    #endregion

    #region Xml转实体
    /// <summary>字符串转为Xml实体对象</summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <param name="xml">Xml字符串</param>
    /// <returns>Xml实体对象</returns>
    public static TEntity? ToXmlEntity<TEntity>(this String xml) where TEntity : class
    {
        return xml.ToXmlEntity(typeof(TEntity)) as TEntity;
    }

    /// <summary>字符串转为Xml实体对象</summary>
    /// <param name="xml">Xml字符串</param>
    /// <param name="type">实体类型</param>
    /// <returns>Xml实体对象</returns>
    public static Object? ToXmlEntity(this String xml, Type type)
    {
        if (xml.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(xml));
        if (type == null) throw new ArgumentNullException(nameof(type));

        var x = new Serialization.Xml
        {
            Stream = new MemoryStream(xml.GetBytes())
        };

        return x.Read(type);

        //if (!type.IsPublic) throw new XException("类型{0}不是public，不能进行Xml序列化！", type.FullName);

        //var serial = new XmlSerializer(type);
        //using (var reader = new StringReader(xml))
        //using (var xr = new XmlTextReader(reader))
        //{
        //    // 必须关闭Normalization，否则字符串的\r\n会变为\n
        //    //xr.Normalization = true;
        //    return serial.Deserialize(xr);
        //}
    }

    /// <summary>数据流转为Xml实体对象</summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <param name="stream">数据流</param>
    /// <param name="encoding">编码</param>
    /// <returns>Xml实体对象</returns>
    public static TEntity? ToXmlEntity<TEntity>(this Stream stream, Encoding? encoding = null) where TEntity : class
    {
        return stream.ToXmlEntity(typeof(TEntity), encoding) as TEntity;
    }

    /// <summary>数据流转为Xml实体对象</summary>
    /// <param name="stream">数据流</param>
    /// <param name="type">实体类型</param>
    /// <param name="encoding">编码</param>
    /// <returns>Xml实体对象</returns>
    public static Object? ToXmlEntity(this Stream stream, Type type, Encoding? encoding = null)
    {
        if (stream == null) throw new ArgumentNullException(nameof(stream));
        if (type == null) throw new ArgumentNullException(nameof(type));
        encoding ??= Encoding.UTF8;

        var x = new Serialization.Xml
        {
            Stream = stream,
            Encoding = encoding
        };

        return x.Read(type);

        //if (!type.IsPublic) throw new XException("类型{0}不是public，不能进行Xml序列化！", type.FullName);

        //var serial = new XmlSerializer(type);
        //using (var reader = new StreamReader(stream, encoding))
        //using (var xr = new XmlTextReader(reader))
        //{
        //    // 必须关闭Normalization，否则字符串的\r\n会变为\n
        //    //xr.Normalization = true;
        //    return serial.Deserialize(xr);
        //}
    }

    /// <summary>Xml文件转为Xml实体对象</summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <param name="file">Xml文件</param>
    /// <param name="encoding">编码</param>
    /// <returns>Xml实体对象</returns>
    public static TEntity? ToXmlFileEntity<TEntity>(this String file, Encoding? encoding = null) where TEntity : class
    {
        if (file.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(file));
        if (!File.Exists(file)) return null;

        using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        return stream.ToXmlEntity<TEntity>(encoding);
    }
    #endregion

    #region Xml类型转换
    ///// <summary>删除字节序，硬编码支持utf-8、utf-32、Unicode三种</summary>
    ///// <param name="encoding">原始编码</param>
    ///// <returns>删除字节序后的编码</returns>
    //internal static Encoding TrimPreamble(this Encoding encoding)
    //{
    //    if (encoding == null) return encoding;

    //    var bts = encoding.GetPreamble();
    //    if (bts == null || bts.Length <= 0) return encoding;

    //    if (encoding is UTF8Encoding) return _utf8Encoding ?? (_utf8Encoding = new UTF8Encoding(false));
    //    if (encoding is UTF32Encoding) return _utf32Encoding ?? (_utf32Encoding = new UTF32Encoding(false, false));
    //    if (encoding is UnicodeEncoding) return _unicodeEncoding ?? (_unicodeEncoding = new UnicodeEncoding(false, false));

    //    return encoding;
    //}
    //private static Encoding _utf8Encoding;
    //private static Encoding _utf32Encoding;
    //private static Encoding _unicodeEncoding;

    internal static Boolean CanXmlConvert(this Type type)
    {
        if (type.IsBaseType()) return true;

        if (!type.IsValueType) return false;

        if (type == typeof(Guid) || type == typeof(DateTimeOffset) || type == typeof(TimeSpan)) return true;

        return false;
    }

    internal static String? XmlConvertToString(Object value)
    {
        if (value == null) return null;

        var type = value.GetType();
        var code = type.GetTypeCode();
        if (code == TypeCode.String) return value.ToString();
        if (code == TypeCode.DateTime) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.RoundtripKind);

        //var method = Reflect.GetMethodEx(typeof(XmlConvert), "ToString", type);
        var method = typeof(XmlConvert).GetMethodEx("ToString", type);
        if (method == null) throw new XException("Type {0} does not support converting to Xml strings. Please use the CanXmlConvert method first to determine!", type);

        return (String?)"".Invoke(method, value);
    }

    internal static T? XmlConvertFromString<T>(String xml) => (T?)XmlConvertFromString(typeof(T), xml);

    internal static Object? XmlConvertFromString(Type type, String xml)
    {
        if (xml == null) return null;

        var code = type.GetTypeCode();
        if (code == TypeCode.String) return xml;
        if (code == TypeCode.DateTime) return XmlConvert.ToDateTime(xml, XmlDateTimeSerializationMode.RoundtripKind);

        //var method = Reflect.GetMethodEx(typeof(XmlConvert), "To" + type.Name, typeof(String));
        var method = typeof(XmlConvert).GetMethodEx("To" + type.Name, typeof(String));
        if (method == null) throw new XException("Type {0} does not support converting from Xml strings. Please use the CanXmlConvert method first!", type);

        return "".Invoke(method, xml);
    }
    #endregion

    #region Xml转字典
    /// <summary>简单Xml转为字符串字典</summary>
    /// <param name="xml"></param>
    /// <returns></returns>
    public static Dictionary<String, String>? ToXmlDictionary(this String xml)
    {
        if (String.IsNullOrEmpty(xml)) return null;

        var doc = new XmlDocument();
        doc.LoadXml(xml);
        var root = doc.DocumentElement;
        if (root == null) return null;

        var dic = new Dictionary<String, String>();

        if (root.ChildNodes?.Count > 0)
        {
            foreach (var item in root.ChildNodes)
            {
                if (item is not XmlNode node) continue;

                if (node.ChildNodes != null && (node.ChildNodes.Count > 1 ||
                    node.ChildNodes.Count == 1 && !(node.FirstChild is XmlText) && !(node.FirstChild is XmlCDataSection)))
                {
                    dic[node.Name] = node.InnerXml;
                }
                else
                {
                    dic[node.Name] = node.InnerText;
                }
            }
        }

        return dic;
    }

    /// <summary>字符串字典转为Xml</summary>
    /// <param name="dic"></param>
    /// <param name="rootName"></param>
    /// <returns></returns>
    public static String ToXml(this IDictionary<String, String> dic, String? rootName = null)
    {
        if (String.IsNullOrEmpty(rootName)) rootName = "xml";

        var doc = new XmlDocument();
        var root = doc.CreateElement(rootName);
        doc.AppendChild(root);

        if (dic?.Count > 0)
        {
            foreach (var item in dic)
            {
                var elm = doc.CreateElement(item.Key);
                elm.InnerText = item.Value;
                root.AppendChild(elm);
            }
        }

        return doc.OuterXml;
    }
    #endregion
}